Introduction

In this tutorial you will be introduced to r5r: an interface to the R5 routing engine developed by Conveyal. R5 allows you to do “rapid, realistic” routing on multimodal networks. It’s very flexible, enabling intermodal analyses and has the key strength of retaining the full detail of the public transport timetable. If you’re more familiar with python, you can consider using r5py instead - although it may not have all of the same functionality.

The tutorial consists of the following main steps:

  1. Installing r5r
  2. GTFS preparation
  3. Building a r5r model
  4. Preparing origin-destination data
  5. Calculating and exporting a TTM

In addition to r5r, you will be exposed to several other useful R packages: dplyr (for working with data tables), sf (for working with geodata), and tidytransit (for working with GTFS data). These are all very powerful packages that can make your life a lot easier if you learn how to use them! You’re encouraged to look into the documentation of these packages for more information.

Tip: you can modify this file and use it as a template to help you complete your project ;)

Installing r5r

All the packages listed below (including r5r) can be installed by clicking “packages > install” on the sidebar and searching for the package name or by executing the following command: install.packages("package_name_here")

Since R5 (the routing engine itself) is written in java, we need to install the Java Development Kit (JDK) to get it to work. Specifically we need version 21. You can install it here: https://www.oracle.com/de/java/technologies/downloads/#java21

Once that’s done, we can load all the packages we’ll be using today.

library(tidyverse)
library(sf)
library(tidytransit)

options(java.parameters = '-Xmx4G') # we need to allocate RAM for r5r to work. You can increase this depending on your system resources.
library(r5r)

For the code to run correctly, you need to set the “working directory”. This should be the repository (folder) you downloaded / cloned from github.

setwd("C:/you_need_to_update_this_path/ilutm-r5r-tutorial/")
Error in setwd("C:/you_need_to_update_this_path/ilutm-r5r-tutorial/") : 
  cannot change working directory

GTFS preparation

  • Download data from: https://gtfs.de/ - pick “Deutschland gesamt”
  • Move the zip file into the /raw_data/ folder
  • Using the full feed for all of Germany will require a lot of resources. Since we’ll be analyzing a single city - lets filter the feed so that it is smaller. We’ll be using tidytransit for this.
  • Let’s first load in geodata representing the area we’ll be trimming to (in this case - Munich). We’ll use st_read() from the sf package for this. It can handle all typical geospatial file types (.shp,.gpkg,etc.)
area <- st_read("./raw_data/munich_admin.gpkg")
Reading layer `boundary_administrative_munich' from data source 
  `C:\Users\barte\Documents\R\ilutm-r5r-tutorial\raw_data\munich_admin.gpkg' using driver `GPKG'
Simple feature collection with 25 features and 2 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 11.36078 ymin: 48.06162 xmax: 11.72291 ymax: 48.24812
Geodetic CRS:  WGS 84
ggplot()+
  geom_sf(data  = area)

  • It’s good practice to buffer our study area to avoid any edge effects. Let’s buffer our area by 5 km. NOTE: in this tutorial we’re just going to be looking at accessibility within Munich. If your destinations extend beyond the border of your study area, you may need to buffer your study area significantly! (>>5 km)
  • We’ve seen how to do this in QGIS, let’s use sf this time
area_buffered <- st_transform(area,crs = 25832) # switch crs to projected (ETRS89 / UTM zone 32N)

area_buffered <- st_union(area_buffered) # join geometries 

area_buffered <- st_buffer(area_buffered, 5000) # buffer by 5km

area_buffered <- st_transform(area_buffered,crs = 4326) #switch back to WGS84 for filtering the GTFS feed


ggplot()+
  geom_sf(data = area_buffered)+
  geom_sf(data = area)

  • Let’s load in the GTFS feed we downloaded and trim it.

gtfs_raw <- read_gtfs("./raw_data/latest.zip",encoding = "UTF-8") # path should point to the GTFS feed you downloaded
|--------------------------------------------------|
|==================================================|
gtfs_filtered <- filter_feed_by_area(gtfs_raw,st_bbox(area_buffered))
  • We can see that the number of stops in the feed has been significantly reduced.
  • Let’s save the filtered feed. R5 will use this to build a network, you can also use it for other public transport analyses (refer to previous tutorials). Tip: tidytransit has a lot of great functionality for this!

write_gtfs(gtfs_filtered,"./r5r_model/gtfs.zip")

Building a r5r model

  • Before running any more code we need to supply R5 the data it needs to build the network.
  • So far we have prepared the GTFS data, but we also need a street network.
  • R5 uses an extract of OSM data to build the street network. To do this, it needs to be provided with a .pbf file. This can be found on Geofabrik or https://slice.openstreetmap.us/.
  • OSM by the slice is recommended because it has a cool name and because you can select the exact area you’re interested in (reducing network size, improving performance)
  • Just as when we were trimming the GTFS feed, extract an area larger than the study area

  • Save the .pbf file to the /r5r_model/ folder. The folder should look like this:

  • We can now tell r5 to build the network.

  • Tip: if you are sure you aren’t modifying the GTFS or .pbf data any further, you can turn overwrite = F to avoid rebuilding the model (faster)

r5r_core <- setup_r5(data_path = "./r5r_model", verbose = FALSE,overwrite = T)

Preparing origin-destination data

  • In this tutorial we’ll use public transport stops as our origins and destinations.
  • As we’ve seen in a previous tutorial, we can get this information directly from the GTFS feed. Let’s do it in R instead of QGIS this time
  • As we discussed in lecture, a single “stop” may be represented by multiple in a GTFS feed (e.g., a bus stop on both sides of the street).
  • We can take some steps to simplify this. We will group all stops that share the same value for parent_station and then keep a single coordinate by taking the average. Be aware that solving this in a line of code is often too good to be true. GTFS data often contains mistakes, and may require more careful cleaning!
stops <- summarize(group_by(gtfs_filtered$stops, parent_station),
                   stop_lat = mean(stop_lat),
                   stop_lon = mean(stop_lon),
                   parent_station_name = first(stop_name))  
stops
  • let’s convert the table to geodata and filter it down to our study area.
stops <- st_as_sf(stops,coords = c("stop_lon","stop_lat"),crs = 4326,remove = F)

stops <-st_filter(stops,area) #apply spatial filter

stops
Simple feature collection with 1125 features and 4 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 11.38896 ymin: 48.06913 xmax: 11.71527 ymax: 48.22
Geodetic CRS:  WGS 84

  • Finally, let’s format our data. The origins and destinations should have an id column. Let’s add a new column called id, and set the values to equal parent_station.
stops <- st_as_sf(stops,coords = c("stop_lon","stop_lat"),crs = 4326,remove = F)

stops <-st_filter(stops,area) #apply spatial filter

stops
Simple feature collection with 1125 features and 4 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 11.38896 ymin: 48.06913 xmax: 11.71527 ymax: 48.22
Geodetic CRS:  WGS 84

Calculating and exporting a TTM

The main functionality of r5r is the ability to calculate a travel time matrix. Knowing the travel time between pairs of points is useful on its own. It can also be used if you want more control over your accessibility calculations. For example, if you calculate a travel time matrix between pairs of public transport stops, you can calculate the access (origin –> first public transport stop) and egress (last public transport stop –> destination) legs of the trip independently. In other words, combining the output of r5r with the QGIS-based accessibility calculations we did in earlier tutorials.

Let’s give it a shot:

tip: there are many other settings you can change, you’re encouraged to read the documentation of r5r to see what is possible! The documentation has many nice examples!

  • stops: our origins are the public transport stops
  • destinations: our destinations are the public transport stops
  • mode: the primary mode is public transport (which includes walking for access and transfers)
  • mode_egress: the egress mode is walking
  • departure_datetime: start time
  • time_window: A travel time matrix will be calculated for each minute, for time_window minutes after the departure time. This allows for us to account for the variability in travel times. Imagine we ran our analysis only at 8:00 and we just missed a departure of an S-Bahn at 7:59, the travel time that we’d calculate would be severely penalized by an excessive wait time. Calculating travel times over a period of time lets us mitigate this. This parameter can be reduced (default is 10) as it can be computationally expensive. It’s increasingly important in suburban and rural areas where service frequencies are low.
  • percentiles: this corresponds to the time_window. a value of 50 will report the median travel time. We can specify multiple percentiles by using a vector c()
  • max_walk_time: max time for access, egress, and transfers by walking
  • walk_speed: (kph)
  • max_ride: maximum # of transfers

TTM<-
travel_time_matrix(
  r5r_core,
  origins = stops, 
  destinations = stops, 
  mode = c("TRANSIT"),  
  mode_egress = "WALK", 
  departure_datetime = as.POSIXct("26-05-2025 9:00:00", format = "%d-%m-%Y %H:%M:%S"), #its important that the date and time is within your GTFS feed
  time_window = 60L, # 
  percentiles = c(10,50),
  max_walk_time = 10,
  max_trip_duration = 30L,
  walk_speed = 4,
  max_rides = 3,
)

TTM

The impact of the time_window parameter can be quite significant on an analysis! In this example we calculated:

  • the 10th percentile travel time (meaning 10% of trips are at least this fast)
  • the 50th percentile travel time (meaning 50% of trips are at least this fast)

The proportion of OD pairs that had a 50th percentile travel time > 30 min and a 10th percentile travel time <= 30 min:

TTM <- mutate(TTM,tt_diff = travel_time_p50 - travel_time_p10)

print(round(sum(is.na(TTM$tt_diff)) / length(TTM$tt_diff),1))
[1] 0.4

For those with a 10th and 50th percentile travel time <= 30 min, the distribution of the difference between the two is as follows:

TTM$tt_diff%>%na.omit()%>%summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   2.000   4.000   3.675   4.000  17.000 
TTM<-TTM%>%select(-tt_diff)

Let’s get the travel time matrix ready for an export. We will restructure the data so that the geometry of the destination stops (to_id) is added to each row. We will also add the name of the origin station so that the data is easier to work with. This is a similar operation to a VLOOKUP in excel. We use the from_id and to_id fields as they represent the parent_station.

For more guidance on dplyr functions (like left_join()) check out this cheat sheet

# join the geometry of the destination station
geometry_to_join <- select(stops,id) # drop unnecessary cols
geometry_to_join <- rename(geometry_to_join,to_id = "id") # rename "id" to "to_id" so that the data is joined as expected

TTM <-
  left_join(TTM, 
            geometry_to_join,
            by = "to_id")


# join the station name of the origin station

names_to_join <- select(stops,parent_station_name,id) # drop unnecessary cols
names_to_join <- st_drop_geometry(names_to_join) # drop geometry
names_to_join <- rename(names_to_join,from_id = "id") # rename "id" to "from_id" so that the data is joined as expected


TTM <-
  left_join(TTM, 
            names_to_join,
            by = "from_id")

TTM <- st_as_sf(TTM)

TTM
Simple feature collection with 295492 features and 5 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 11.38896 ymin: 48.06913 xmax: 11.71527 ymax: 48.22
Geodetic CRS:  WGS 84
First 10 features:
   from_id  to_id travel_time_p10 travel_time_p50 parent_station_name                  geometry
1   100060 100060               0               0   Josef-Lang-Straße POINT (11.47202 48.15011)
2   100060  10157              28              NA   Josef-Lang-Straße POINT (11.46016 48.18474)
3   100060 105596              29              NA   Josef-Lang-Straße POINT (11.48913 48.18399)
4   100060 109574              23              NA   Josef-Lang-Straße POINT (11.47472 48.12805)
5   100060  11520              25              NA   Josef-Lang-Straße POINT (11.54544 48.14428)
6   100060 128167              23              NA   Josef-Lang-Straße POINT (11.46642 48.19177)
7   100060 131549              21              25   Josef-Lang-Straße POINT (11.44335 48.16361)
8   100060 131638              22              NA   Josef-Lang-Straße POINT (11.47278 48.17485)
9   100060 132365              19              25   Josef-Lang-Straße  POINT (11.4531 48.14563)
10  100060   1335              29              NA   Josef-Lang-Straße POINT (11.45716 48.20257)

This data format is easier to interpret if we look at a single stop. For example, lets find all stops that can be reached from Theresienstraße within a median travel time of <= 20 minutes.

ts_stops_20 <- filter(TTM,parent_station_name == "Theresienstraße", travel_time_p50 <= 20)


ggplot()+
  geom_sf(data = area)+
  geom_sf(data = ts_stops_20, color = "lightblue")+
  geom_sf(data = filter(stops,parent_station_name == "Theresienstraße"), color = "blue")

Let’s export this (& the stops) so that we can work with it in QGIS.

st_write(TTM,"./output/TTM.gpkg",append = F)
Deleting layer `TTM' using driver `GPKG'
Writing layer `TTM' to data source `./output/TTM.gpkg' using driver `GPKG'
Writing 295492 features with 5 fields and geometry type Point.
st_write(stops, "./output/stops.gpkg", append = F)
Deleting layer `stops' using driver `GPKG'
Writing layer `stops' to data source `./output/stops.gpkg' using driver `GPKG'
Writing 1125 features with 5 fields and geometry type Point.

We can achieve the same result as above by doing the following:

Then, by using what you’ve learned in previous tutorials, you can create service areas:

We’ve just scratched the surface of what you can do with r5r and tidytransit. Consider taking a look through the documentation for some inspiration for your project!

LS0tDQp0aXRsZTogIlI1UiBUdXRvcmlhbCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UpIA0KYGBgDQoNCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9DQpsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KIyBJbnRyb2R1Y3Rpb24NCg0KSW4gdGhpcyB0dXRvcmlhbCB5b3Ugd2lsbCBiZSBpbnRyb2R1Y2VkIHRvIFtyNXJdKGh0dHBzOi8vaXBlYWdpdC5naXRodWIuaW8vcjVyLyk6IGFuIGludGVyZmFjZSB0byB0aGUgW1I1XShodHRwczovL2dpdGh1Yi5jb20vY29udmV5YWwvcjUpIHJvdXRpbmcgZW5naW5lIGRldmVsb3BlZCBieSBDb252ZXlhbC4gUjUgYWxsb3dzIHlvdSB0byBkbyAicmFwaWQsIHJlYWxpc3RpYyIgcm91dGluZyBvbiBtdWx0aW1vZGFsIG5ldHdvcmtzLiBJdCdzIHZlcnkgZmxleGlibGUsIGVuYWJsaW5nIGludGVybW9kYWwgYW5hbHlzZXMgYW5kIGhhcyB0aGUga2V5IHN0cmVuZ3RoIG9mIHJldGFpbmluZyB0aGUgZnVsbCBkZXRhaWwgb2YgdGhlIHB1YmxpYyB0cmFuc3BvcnQgdGltZXRhYmxlLiBJZiB5b3UncmUgbW9yZSBmYW1pbGlhciB3aXRoIHB5dGhvbiwgeW91IGNhbiBjb25zaWRlciB1c2luZyBbcjVweV0oaHR0cHM6Ly9naXRodWIuY29tL3I1cHkvcjVweSkgaW5zdGVhZCAtIGFsdGhvdWdoIGl0IG1heSBub3QgaGF2ZSBhbGwgb2YgdGhlIHNhbWUgZnVuY3Rpb25hbGl0eS4NCg0KVGhlIHR1dG9yaWFsIGNvbnNpc3RzIG9mIHRoZSBmb2xsb3dpbmcgbWFpbiBzdGVwczoNCg0KMS4gIEluc3RhbGxpbmcgYHI1cmANCjIuICBHVEZTIHByZXBhcmF0aW9uDQozLiAgQnVpbGRpbmcgYSBgcjVyYCBtb2RlbA0KNC4gIFByZXBhcmluZyBvcmlnaW4tZGVzdGluYXRpb24gZGF0YQ0KNS4gIENhbGN1bGF0aW5nIGFuZCBleHBvcnRpbmcgYSBUVE0NCg0KSW4gYWRkaXRpb24gdG8gcjVyLCB5b3Ugd2lsbCBiZSBleHBvc2VkIHRvIHNldmVyYWwgb3RoZXIgdXNlZnVsIFIgcGFja2FnZXM6IFtkcGx5cl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnLykgKGZvciB3b3JraW5nIHdpdGggZGF0YSB0YWJsZXMpLCBbc2ZdKGh0dHBzOi8vci1zcGF0aWFsLmdpdGh1Yi5pby9zZi8pIChmb3Igd29ya2luZyB3aXRoIGdlb2RhdGEpLCBhbmQgW3RpZHl0cmFuc2l0XShodHRwczovL2dpdGh1Yi5jb20vci10cmFuc2l0L3RpZHl0cmFuc2l0KSAoZm9yIHdvcmtpbmcgd2l0aCBHVEZTIGRhdGEpLiBUaGVzZSBhcmUgYWxsIHZlcnkgcG93ZXJmdWwgcGFja2FnZXMgdGhhdCBjYW4gbWFrZSB5b3VyIGxpZmUgYSBsb3QgZWFzaWVyIGlmIHlvdSBsZWFybiBob3cgdG8gdXNlIHRoZW0hIFlvdSdyZSBlbmNvdXJhZ2VkIHRvIGxvb2sgaW50byB0aGUgZG9jdW1lbnRhdGlvbiBvZiB0aGVzZSBwYWNrYWdlcyBmb3IgbW9yZSBpbmZvcm1hdGlvbi4NCg0KKipUaXA6IHlvdSBjYW4gbW9kaWZ5IHRoaXMgZmlsZSBhbmQgdXNlIGl0IGFzIGEgdGVtcGxhdGUgdG8gaGVscCB5b3UgY29tcGxldGUgeW91ciBwcm9qZWN0IDspKioNCg0KIyBJbnN0YWxsaW5nIGByNXJgDQoNCkFsbCB0aGUgcGFja2FnZXMgbGlzdGVkIGJlbG93IChpbmNsdWRpbmcgYHI1cmApIGNhbiBiZSBpbnN0YWxsZWQgYnkgY2xpY2tpbmcgInBhY2thZ2VzIFw+IGluc3RhbGwiIG9uIHRoZSBzaWRlYmFyIGFuZCBzZWFyY2hpbmcgZm9yIHRoZSBwYWNrYWdlIG5hbWUgb3IgYnkgZXhlY3V0aW5nIHRoZSBmb2xsb3dpbmcgY29tbWFuZDogYGluc3RhbGwucGFja2FnZXMoInBhY2thZ2VfbmFtZV9oZXJlIilgDQoNCiFbXSguL2ltZy9pbnN0YWxsX3BhY2thZ2VzLnBuZyl7d2lkdGg9IjMwMHB4In0NCg0KU2luY2UgUjUgKHRoZSByb3V0aW5nIGVuZ2luZSBpdHNlbGYpIGlzIHdyaXR0ZW4gaW4gamF2YSwgd2UgbmVlZCB0byBpbnN0YWxsIHRoZSBKYXZhIERldmVsb3BtZW50IEtpdCAoSkRLKSB0byBnZXQgaXQgdG8gd29yay4gU3BlY2lmaWNhbGx5IHdlIG5lZWQgdmVyc2lvbiAqKjIxKiouIFlvdSBjYW4gaW5zdGFsbCBpdCBoZXJlOiA8aHR0cHM6Ly93d3cub3JhY2xlLmNvbS9kZS9qYXZhL3RlY2hub2xvZ2llcy9kb3dubG9hZHMvI2phdmEyMT4NCg0KT25jZSB0aGF0J3MgZG9uZSwgd2UgY2FuIGxvYWQgYWxsIHRoZSBwYWNrYWdlcyB3ZSdsbCBiZSB1c2luZyB0b2RheS4NCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KHRpZHl0cmFuc2l0KQ0KDQpvcHRpb25zKGphdmEucGFyYW1ldGVycyA9ICctWG14NEcnKSAjIHdlIG5lZWQgdG8gYWxsb2NhdGUgUkFNIGZvciByNXIgdG8gd29yay4gWW91IGNhbiBpbmNyZWFzZSB0aGlzIGRlcGVuZGluZyBvbiB5b3VyIHN5c3RlbSByZXNvdXJjZXMuDQpsaWJyYXJ5KHI1cikNCg0KYGBgDQoNCkZvciB0aGUgY29kZSB0byBydW4gY29ycmVjdGx5LCB5b3UgbmVlZCB0byBzZXQgdGhlICJ3b3JraW5nIGRpcmVjdG9yeSIuIFRoaXMgc2hvdWxkIGJlIHRoZSByZXBvc2l0b3J5IChmb2xkZXIpIHlvdSBkb3dubG9hZGVkIC8gY2xvbmVkIGZyb20gZ2l0aHViLg0KDQpgYGB7cn0NCnNldHdkKCJDOi95b3VfbmVlZF90b191cGRhdGVfdGhpc19wYXRoL2lsdXRtLXI1ci10dXRvcmlhbC8iKQ0KYGBgDQoNCiMgR1RGUyBwcmVwYXJhdGlvbg0KDQotICAgRG93bmxvYWQgZGF0YSBmcm9tOiA8aHR0cHM6Ly9ndGZzLmRlLz4gLSBwaWNrICJEZXV0c2NobGFuZCBnZXNhbXQiDQotICAgTW92ZSB0aGUgemlwIGZpbGUgaW50byB0aGUgYC9yYXdfZGF0YS9gIGZvbGRlcg0KLSAgIFVzaW5nIHRoZSBmdWxsIGZlZWQgZm9yIGFsbCBvZiBHZXJtYW55IHdpbGwgcmVxdWlyZSBhIGxvdCBvZiByZXNvdXJjZXMuIFNpbmNlIHdlJ2xsIGJlIGFuYWx5emluZyBhIHNpbmdsZSBjaXR5IC0gbGV0cyBmaWx0ZXIgdGhlIGZlZWQgc28gdGhhdCBpdCBpcyBzbWFsbGVyLiBXZSdsbCBiZSB1c2luZyBgdGlkeXRyYW5zaXRgIGZvciB0aGlzLg0KLSAgIExldCdzIGZpcnN0IGxvYWQgaW4gZ2VvZGF0YSByZXByZXNlbnRpbmcgdGhlIGFyZWEgd2UnbGwgYmUgdHJpbW1pbmcgdG8gKGluIHRoaXMgY2FzZSAtIE11bmljaCkuIFdlJ2xsIHVzZSBgc3RfcmVhZCgpYCBmcm9tIHRoZSBgc2ZgIHBhY2thZ2UgZm9yIHRoaXMuIEl0IGNhbiBoYW5kbGUgYWxsIHR5cGljYWwgZ2Vvc3BhdGlhbCBmaWxlIHR5cGVzIChgLnNocGAsYC5ncGtnYCxldGMuKQ0KDQpgYGB7cn0NCmFyZWEgPC0gc3RfcmVhZCgiLi9yYXdfZGF0YS9tdW5pY2hfYWRtaW4uZ3BrZyIpDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgID0gYXJlYSkNCg0KYGBgDQoNCi0gICBJdCdzIGdvb2QgcHJhY3RpY2UgdG8gYnVmZmVyIG91ciBzdHVkeSBhcmVhIHRvIGF2b2lkIGFueSBlZGdlIGVmZmVjdHMuIExldCdzIGJ1ZmZlciBvdXIgYXJlYSBieSA1IGttLiBOT1RFOiBpbiB0aGlzIHR1dG9yaWFsIHdlJ3JlIGp1c3QgZ29pbmcgdG8gYmUgbG9va2luZyBhdCBhY2Nlc3NpYmlsaXR5IHdpdGhpbiBNdW5pY2guIElmIHlvdXIgZGVzdGluYXRpb25zIGV4dGVuZCBiZXlvbmQgdGhlIGJvcmRlciBvZiB5b3VyIHN0dWR5IGFyZWEsIHlvdSBtYXkgbmVlZCB0byBidWZmZXIgeW91ciBzdHVkeSBhcmVhIHNpZ25pZmljYW50bHkhIChcPlw+NSBrbSkNCi0gICBXZSd2ZSBzZWVuIGhvdyB0byBkbyB0aGlzIGluIFFHSVMsIGxldCdzIHVzZSBgc2ZgIHRoaXMgdGltZQ0KDQpgYGB7cn0NCmFyZWFfYnVmZmVyZWQgPC0gc3RfdHJhbnNmb3JtKGFyZWEsY3JzID0gMjU4MzIpICMgc3dpdGNoIGNycyB0byBwcm9qZWN0ZWQgKEVUUlM4OSAvIFVUTSB6b25lIDMyTikNCg0KYXJlYV9idWZmZXJlZCA8LSBzdF91bmlvbihhcmVhX2J1ZmZlcmVkKSAjIGpvaW4gZ2VvbWV0cmllcyANCg0KYXJlYV9idWZmZXJlZCA8LSBzdF9idWZmZXIoYXJlYV9idWZmZXJlZCwgNTAwMCkgIyBidWZmZXIgYnkgNWttDQoNCmFyZWFfYnVmZmVyZWQgPC0gc3RfdHJhbnNmb3JtKGFyZWFfYnVmZmVyZWQsY3JzID0gNDMyNikgI3N3aXRjaCBiYWNrIHRvIFdHUzg0IGZvciBmaWx0ZXJpbmcgdGhlIEdURlMgZmVlZA0KDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhX2J1ZmZlcmVkKSsNCiAgZ2VvbV9zZihkYXRhID0gYXJlYSkNCmBgYA0KDQotICAgTGV0J3MgbG9hZCBpbiB0aGUgR1RGUyBmZWVkIHdlIGRvd25sb2FkZWQgYW5kIHRyaW0gaXQuDQoNCmBgYHtyfQ0KDQpndGZzX3JhdyA8LSByZWFkX2d0ZnMoIi4vcmF3X2RhdGEvbGF0ZXN0LnppcCIsZW5jb2RpbmcgPSAiVVRGLTgiKSAjIHBhdGggc2hvdWxkIHBvaW50IHRvIHRoZSBHVEZTIGZlZWQgeW91IGRvd25sb2FkZWQNCg0KZ3Rmc19maWx0ZXJlZCA8LSBmaWx0ZXJfZmVlZF9ieV9hcmVhKGd0ZnNfcmF3LHN0X2Jib3goYXJlYV9idWZmZXJlZCkpDQoNCmBgYA0KDQotICAgV2UgY2FuIHNlZSB0aGF0IHRoZSBudW1iZXIgb2Ygc3RvcHMgaW4gdGhlIGZlZWQgaGFzIGJlZW4gc2lnbmlmaWNhbnRseSByZWR1Y2VkLg0KDQpgYGB7ciBlY2hvPUZBTFNFfQ0KDQp0cmliYmxlKH5uX3N0b3BzX3Jhdywgfm5fc3RvcHNfZmlsdGVyZWQsDQogICAgICAgIG5yb3coZ3Rmc19yYXckc3RvcHMpLCBucm93KGd0ZnNfZmlsdGVyZWQkc3RvcHMpKQ0KYGBgDQoNCi0gICBMZXQncyBzYXZlIHRoZSBmaWx0ZXJlZCBmZWVkLiBSNSB3aWxsIHVzZSB0aGlzIHRvIGJ1aWxkIGEgbmV0d29yaywgeW91IGNhbiBhbHNvIHVzZSBpdCBmb3Igb3RoZXIgcHVibGljIHRyYW5zcG9ydCBhbmFseXNlcyAocmVmZXIgdG8gcHJldmlvdXMgdHV0b3JpYWxzKS4gVGlwOiBgdGlkeXRyYW5zaXRgIGhhcyBhIGxvdCBvZiBncmVhdCBmdW5jdGlvbmFsaXR5IGZvciB0aGlzIQ0KDQpgYGB7cn0NCg0Kd3JpdGVfZ3RmcyhndGZzX2ZpbHRlcmVkLCIuL3I1cl9tb2RlbC9ndGZzLnppcCIpDQoNCmBgYA0KDQojIEJ1aWxkaW5nIGEgYHI1cmAgbW9kZWwNCg0KLSAgIEJlZm9yZSBydW5uaW5nIGFueSBtb3JlIGNvZGUgd2UgbmVlZCB0byBzdXBwbHkgUjUgdGhlIGRhdGEgaXQgbmVlZHMgdG8gYnVpbGQgdGhlIG5ldHdvcmsuDQotICAgU28gZmFyIHdlIGhhdmUgcHJlcGFyZWQgdGhlIEdURlMgZGF0YSwgYnV0IHdlIGFsc28gbmVlZCBhIHN0cmVldCBuZXR3b3JrLg0KLSAgIFI1IHVzZXMgYW4gZXh0cmFjdCBvZiBPU00gZGF0YSB0byBidWlsZCB0aGUgc3RyZWV0IG5ldHdvcmsuIFRvIGRvIHRoaXMsIGl0IG5lZWRzIHRvIGJlIHByb3ZpZGVkIHdpdGggYSBgLnBiZmAgZmlsZS4gVGhpcyBjYW4gYmUgZm91bmQgb24gW0dlb2ZhYnJpa10oaHR0cDovL2Rvd25sb2FkLmdlb2ZhYnJpay5kZS8pIG9yIDxodHRwczovL3NsaWNlLm9wZW5zdHJlZXRtYXAudXMvPi4NCi0gICBPU00gYnkgdGhlIHNsaWNlIGlzIHJlY29tbWVuZGVkIGJlY2F1c2UgaXQgaGFzIGEgY29vbCBuYW1lIGFuZCBiZWNhdXNlIHlvdSBjYW4gc2VsZWN0IHRoZSBleGFjdCBhcmVhIHlvdSdyZSBpbnRlcmVzdGVkIGluIChyZWR1Y2luZyBuZXR3b3JrIHNpemUsIGltcHJvdmluZyBwZXJmb3JtYW5jZSkNCi0gICBKdXN0IGFzIHdoZW4gd2Ugd2VyZSB0cmltbWluZyB0aGUgR1RGUyBmZWVkLCBleHRyYWN0IGFuIGFyZWEgbGFyZ2VyIHRoYW4gdGhlIHN0dWR5IGFyZWENCg0KIVtdKC4vaW1nL3NsaWNlLnBuZykNCg0KLSAgIFNhdmUgdGhlIGAucGJmYCBmaWxlIHRvIHRoZSBgL3I1cl9tb2RlbC9gIGZvbGRlci4gVGhlIGZvbGRlciBzaG91bGQgbG9vayBsaWtlIHRoaXM6ICFbXSguL2ltZy9yNXJfZm9sZGVyLnBuZykNCg0KLSAgIFdlIGNhbiBub3cgdGVsbCByNSB0byBidWlsZCB0aGUgbmV0d29yay4NCg0KLSAgIFRpcDogaWYgeW91IGFyZSBzdXJlIHlvdSBhcmVuJ3QgbW9kaWZ5aW5nIHRoZSBHVEZTIG9yIGAucGJmYCBkYXRhIGFueSBmdXJ0aGVyLCB5b3UgY2FuIHR1cm4gYG92ZXJ3cml0ZSA9IEZgIHRvIGF2b2lkIHJlYnVpbGRpbmcgdGhlIG1vZGVsIChmYXN0ZXIpDQoNCmBgYHtyfQ0KcjVyX2NvcmUgPC0gc2V0dXBfcjUoZGF0YV9wYXRoID0gIi4vcjVyX21vZGVsIiwgdmVyYm9zZSA9IEZBTFNFLG92ZXJ3cml0ZSA9IFQpDQpgYGANCg0KIyBQcmVwYXJpbmcgb3JpZ2luLWRlc3RpbmF0aW9uIGRhdGENCg0KLSAgIEluIHRoaXMgdHV0b3JpYWwgd2UnbGwgdXNlIHB1YmxpYyB0cmFuc3BvcnQgc3RvcHMgYXMgb3VyIG9yaWdpbnMgYW5kIGRlc3RpbmF0aW9ucy4NCi0gICBBcyB3ZSd2ZSBzZWVuIGluIGEgcHJldmlvdXMgdHV0b3JpYWwsIHdlIGNhbiBnZXQgdGhpcyBpbmZvcm1hdGlvbiBkaXJlY3RseSBmcm9tIHRoZSBHVEZTIGZlZWQuIExldCdzIGRvIGl0IGluIFIgaW5zdGVhZCBvZiBRR0lTIHRoaXMgdGltZQ0KLSAgIEFzIHdlIGRpc2N1c3NlZCBpbiBsZWN0dXJlLCBhIHNpbmdsZSAic3RvcCIgbWF5IGJlIHJlcHJlc2VudGVkIGJ5IG11bHRpcGxlIGluIGEgR1RGUyBmZWVkIChlLmcuLCBhIGJ1cyBzdG9wIG9uIGJvdGggc2lkZXMgb2YgdGhlIHN0cmVldCkuDQotICAgV2UgY2FuIHRha2Ugc29tZSBzdGVwcyB0byBzaW1wbGlmeSB0aGlzLiBXZSB3aWxsIGdyb3VwIGFsbCBzdG9wcyB0aGF0IHNoYXJlIHRoZSBzYW1lIHZhbHVlIGZvciBgcGFyZW50X3N0YXRpb25gIGFuZCB0aGVuIGtlZXAgYSBzaW5nbGUgY29vcmRpbmF0ZSBieSB0YWtpbmcgdGhlIGF2ZXJhZ2UuIEJlIGF3YXJlIHRoYXQgc29sdmluZyB0aGlzIGluIGEgbGluZSBvZiBjb2RlIGlzIG9mdGVuIHRvbyBnb29kIHRvIGJlIHRydWUuIEdURlMgZGF0YSBvZnRlbiBjb250YWlucyBtaXN0YWtlcywgYW5kIG1heSByZXF1aXJlIG1vcmUgY2FyZWZ1bCBjbGVhbmluZyENCg0KYGBge3J9DQpzdG9wcyA8LSBzdW1tYXJpemUoZ3JvdXBfYnkoZ3Rmc19maWx0ZXJlZCRzdG9wcywgcGFyZW50X3N0YXRpb24pLA0KICAgICAgICAgICAgICAgICAgIHN0b3BfbGF0ID0gbWVhbihzdG9wX2xhdCksDQogICAgICAgICAgICAgICAgICAgc3RvcF9sb24gPSBtZWFuKHN0b3BfbG9uKSwNCiAgICAgICAgICAgICAgICAgICBwYXJlbnRfc3RhdGlvbl9uYW1lID0gZmlyc3Qoc3RvcF9uYW1lKSkgIA0Kc3RvcHMNCmBgYA0KDQotICAgbGV0J3MgY29udmVydCB0aGUgdGFibGUgdG8gZ2VvZGF0YSBhbmQgZmlsdGVyIGl0IGRvd24gdG8gb3VyIHN0dWR5IGFyZWEuDQoNCmBgYHtyfQ0Kc3RvcHMgPC0gc3RfYXNfc2Yoc3RvcHMsY29vcmRzID0gYygic3RvcF9sb24iLCJzdG9wX2xhdCIpLGNycyA9IDQzMjYscmVtb3ZlID0gRikNCg0Kc3RvcHMgPC1zdF9maWx0ZXIoc3RvcHMsYXJlYSkgI2FwcGx5IHNwYXRpYWwgZmlsdGVyDQoNCnN0b3BzDQpgYGANCg0KYGBge3IgZWNobz1GQUxTRX0NCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKSsNCiAgZ2VvbV9zZihkYXRhID0gc3RvcHMsIGNvbG9yID0gImJsdWUiKQ0KYGBgDQoNCi0gICBGaW5hbGx5LCBsZXQncyBmb3JtYXQgb3VyIGRhdGEuIFRoZSBvcmlnaW5zIGFuZCBkZXN0aW5hdGlvbnMgc2hvdWxkIGhhdmUgYW4gYGlkYCBjb2x1bW4uIExldCdzIGFkZCBhIG5ldyBjb2x1bW4gY2FsbGVkIGBpZGAsIGFuZCBzZXQgdGhlIHZhbHVlcyB0byBlcXVhbCBgcGFyZW50X3N0YXRpb25gLg0KDQpgYGB7cn0NCnN0b3BzIDwtIG11dGF0ZShzdG9wcywgaWQgPSBwYXJlbnRfc3RhdGlvbikNCg0Kc3RvcHMNCmBgYA0KDQojIENhbGN1bGF0aW5nIGFuZCBleHBvcnRpbmcgYSBUVE0NCg0KVGhlIG1haW4gZnVuY3Rpb25hbGl0eSBvZiBgcjVyYCBpcyB0aGUgYWJpbGl0eSB0byBjYWxjdWxhdGUgYSB0cmF2ZWwgdGltZSBtYXRyaXguIEtub3dpbmcgdGhlIHRyYXZlbCB0aW1lIGJldHdlZW4gcGFpcnMgb2YgcG9pbnRzIGlzIHVzZWZ1bCBvbiBpdHMgb3duLiBJdCBjYW4gYWxzbyBiZSB1c2VkIGlmIHlvdSB3YW50IG1vcmUgY29udHJvbCBvdmVyIHlvdXIgYWNjZXNzaWJpbGl0eSBjYWxjdWxhdGlvbnMuIEZvciBleGFtcGxlLCBpZiB5b3UgY2FsY3VsYXRlIGEgdHJhdmVsIHRpbWUgbWF0cml4IGJldHdlZW4gcGFpcnMgb2YgcHVibGljIHRyYW5zcG9ydCBzdG9wcywgeW91IGNhbiBjYWxjdWxhdGUgdGhlIGFjY2VzcyAob3JpZ2luIC0tXD4gZmlyc3QgcHVibGljIHRyYW5zcG9ydCBzdG9wKSBhbmQgZWdyZXNzIChsYXN0IHB1YmxpYyB0cmFuc3BvcnQgc3RvcCAtLVw+IGRlc3RpbmF0aW9uKSBsZWdzIG9mIHRoZSB0cmlwIGluZGVwZW5kZW50bHkuIEluIG90aGVyIHdvcmRzLCBjb21iaW5pbmcgdGhlIG91dHB1dCBvZiByNXIgd2l0aCB0aGUgUUdJUy1iYXNlZCBhY2Nlc3NpYmlsaXR5IGNhbGN1bGF0aW9ucyB3ZSBkaWQgaW4gZWFybGllciB0dXRvcmlhbHMuDQoNCkxldCdzIGdpdmUgaXQgYSBzaG90Og0KDQp0aXA6IHRoZXJlIGFyZSBtYW55IG90aGVyIHNldHRpbmdzIHlvdSBjYW4gY2hhbmdlLCB5b3UncmUgZW5jb3VyYWdlZCB0byByZWFkIHRoZSBkb2N1bWVudGF0aW9uIG9mIGByNXJgIHRvIHNlZSB3aGF0IGlzIHBvc3NpYmxlISBUaGUgW2RvY3VtZW50YXRpb25dKGh0dHBzOi8vaXBlYWdpdC5naXRodWIuaW8vcjVyLykgaGFzIG1hbnkgbmljZSBleGFtcGxlcyENCg0KLSAgIGBzdG9wc2A6IG91ciBvcmlnaW5zIGFyZSB0aGUgcHVibGljIHRyYW5zcG9ydCBzdG9wcw0KLSAgIGBkZXN0aW5hdGlvbnNgOiBvdXIgZGVzdGluYXRpb25zIGFyZSB0aGUgcHVibGljIHRyYW5zcG9ydCBzdG9wcw0KLSAgIGBtb2RlYDogdGhlIHByaW1hcnkgbW9kZSBpcyBwdWJsaWMgdHJhbnNwb3J0ICh3aGljaCBpbmNsdWRlcyB3YWxraW5nIGZvciBhY2Nlc3MgYW5kIHRyYW5zZmVycykNCi0gICBgbW9kZV9lZ3Jlc3NgOiB0aGUgZWdyZXNzIG1vZGUgaXMgd2Fsa2luZw0KLSAgIGBkZXBhcnR1cmVfZGF0ZXRpbWVgOiBzdGFydCB0aW1lDQotICAgYHRpbWVfd2luZG93YDogQSB0cmF2ZWwgdGltZSBtYXRyaXggd2lsbCBiZSBjYWxjdWxhdGVkIGZvciBlYWNoIG1pbnV0ZSwgZm9yIGB0aW1lX3dpbmRvd2AgbWludXRlcyBhZnRlciB0aGUgZGVwYXJ0dXJlIHRpbWUuIFRoaXMgYWxsb3dzIGZvciB1cyB0byBhY2NvdW50IGZvciB0aGUgdmFyaWFiaWxpdHkgaW4gdHJhdmVsIHRpbWVzLiBJbWFnaW5lIHdlIHJhbiBvdXIgYW5hbHlzaXMgb25seSBhdCA4OjAwIGFuZCB3ZSBqdXN0IG1pc3NlZCBhIGRlcGFydHVyZSBvZiBhbiBTLUJhaG4gYXQgNzo1OSwgdGhlIHRyYXZlbCB0aW1lIHRoYXQgd2UnZCBjYWxjdWxhdGUgd291bGQgYmUgc2V2ZXJlbHkgcGVuYWxpemVkIGJ5IGFuIGV4Y2Vzc2l2ZSB3YWl0IHRpbWUuIENhbGN1bGF0aW5nIHRyYXZlbCB0aW1lcyBvdmVyIGEgcGVyaW9kIG9mIHRpbWUgbGV0cyB1cyBtaXRpZ2F0ZSB0aGlzLiBUaGlzIHBhcmFtZXRlciBjYW4gYmUgcmVkdWNlZCAoZGVmYXVsdCBpcyAxMCkgYXMgaXQgY2FuIGJlIGNvbXB1dGF0aW9uYWxseSBleHBlbnNpdmUuIEl0J3MgaW5jcmVhc2luZ2x5IGltcG9ydGFudCBpbiBzdWJ1cmJhbiBhbmQgcnVyYWwgYXJlYXMgd2hlcmUgc2VydmljZSBmcmVxdWVuY2llcyBhcmUgbG93Lg0KLSAgIGBwZXJjZW50aWxlc2A6IHRoaXMgY29ycmVzcG9uZHMgdG8gdGhlIGB0aW1lX3dpbmRvd2AuIGEgdmFsdWUgb2YgNTAgd2lsbCByZXBvcnQgdGhlIG1lZGlhbiB0cmF2ZWwgdGltZS4gV2UgY2FuIHNwZWNpZnkgbXVsdGlwbGUgcGVyY2VudGlsZXMgYnkgdXNpbmcgYSB2ZWN0b3IgYGMoKWANCi0gICBgbWF4X3dhbGtfdGltZWA6IG1heCB0aW1lIGZvciBhY2Nlc3MsIGVncmVzcywgYW5kIHRyYW5zZmVycyBieSB3YWxraW5nDQotICAgYHdhbGtfc3BlZWRgOiAoa3BoKQ0KLSAgIGBtYXhfcmlkZWA6IG1heGltdW0gXCMgb2YgdHJhbnNmZXJzDQoNCmBgYHtyfQ0KDQpUVE08LQ0KdHJhdmVsX3RpbWVfbWF0cml4KA0KICByNXJfY29yZSwNCiAgb3JpZ2lucyA9IHN0b3BzLCANCiAgZGVzdGluYXRpb25zID0gc3RvcHMsIA0KICBtb2RlID0gYygiVFJBTlNJVCIpLCAgDQogIG1vZGVfZWdyZXNzID0gIldBTEsiLCANCiAgZGVwYXJ0dXJlX2RhdGV0aW1lID0gYXMuUE9TSVhjdCgiMjYtMDUtMjAyNSA5OjAwOjAwIiwgZm9ybWF0ID0gIiVkLSVtLSVZICVIOiVNOiVTIiksICNpdHMgaW1wb3J0YW50IHRoYXQgdGhlIGRhdGUgYW5kIHRpbWUgaXMgd2l0aGluIHlvdXIgR1RGUyBmZWVkDQogIHRpbWVfd2luZG93ID0gNjBMLCAjIA0KICBwZXJjZW50aWxlcyA9IGMoMTAsNTApLA0KICBtYXhfd2Fsa190aW1lID0gMTAsDQogIG1heF90cmlwX2R1cmF0aW9uID0gMzBMLA0KICB3YWxrX3NwZWVkID0gNCwNCiAgbWF4X3JpZGVzID0gMywNCikNCg0KVFRNDQpgYGANCg0KVGhlIGltcGFjdCBvZiB0aGUgYHRpbWVfd2luZG93YCBwYXJhbWV0ZXIgY2FuIGJlIHF1aXRlIHNpZ25pZmljYW50IG9uIGFuIGFuYWx5c2lzISBJbiB0aGlzIGV4YW1wbGUgd2UgY2FsY3VsYXRlZDoNCg0KLSAgIHRoZSAxMHRoIHBlcmNlbnRpbGUgdHJhdmVsIHRpbWUgKG1lYW5pbmcgMTAlIG9mIHRyaXBzIGFyZSBhdCBsZWFzdCB0aGlzIGZhc3QpDQotICAgdGhlIDUwdGggcGVyY2VudGlsZSB0cmF2ZWwgdGltZSAobWVhbmluZyA1MCUgb2YgdHJpcHMgYXJlIGF0IGxlYXN0IHRoaXMgZmFzdCkNCg0KVGhlIHByb3BvcnRpb24gb2YgT0QgcGFpcnMgdGhhdCBoYWQgYSA1MHRoIHBlcmNlbnRpbGUgdHJhdmVsIHRpbWUgXD4gMzAgbWluIGFuZCBhIDEwdGggcGVyY2VudGlsZSB0cmF2ZWwgdGltZSBcPD0gMzAgbWluOg0KDQpgYGB7cn0NClRUTSA8LSBtdXRhdGUoVFRNLHR0X2RpZmYgPSB0cmF2ZWxfdGltZV9wNTAgLSB0cmF2ZWxfdGltZV9wMTApDQoNCnByaW50KHJvdW5kKHN1bShpcy5uYShUVE0kdHRfZGlmZikpIC8gbGVuZ3RoKFRUTSR0dF9kaWZmKSwxKSkNCmBgYA0KDQpGb3IgdGhvc2Ugd2l0aCBhIDEwdGggYW5kIDUwdGggcGVyY2VudGlsZSB0cmF2ZWwgdGltZSBcPD0gMzAgbWluLCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHR3byBpcyBhcyBmb2xsb3dzOg0KDQpgYGB7cn0NClRUTSR0dF9kaWZmJT4lbmEub21pdCgpJT4lc3VtbWFyeSgpDQoNClRUTTwtVFRNJT4lc2VsZWN0KC10dF9kaWZmKQ0KYGBgDQoNCkxldCdzIGdldCB0aGUgdHJhdmVsIHRpbWUgbWF0cml4IHJlYWR5IGZvciBhbiBleHBvcnQuIFdlIHdpbGwgcmVzdHJ1Y3R1cmUgdGhlIGRhdGEgc28gdGhhdCB0aGUgZ2VvbWV0cnkgb2YgdGhlIGRlc3RpbmF0aW9uIHN0b3BzIChgdG9faWRgKSBpcyBhZGRlZCB0byBlYWNoIHJvdy4gV2Ugd2lsbCBhbHNvIGFkZCB0aGUgbmFtZSBvZiB0aGUgb3JpZ2luIHN0YXRpb24gc28gdGhhdCB0aGUgZGF0YSBpcyBlYXNpZXIgdG8gd29yayB3aXRoLiBUaGlzIGlzIGEgc2ltaWxhciBvcGVyYXRpb24gdG8gYSBWTE9PS1VQIGluIGV4Y2VsLiBXZSB1c2UgdGhlIGBmcm9tX2lkYCBhbmQgYHRvX2lkYCBmaWVsZHMgYXMgdGhleSByZXByZXNlbnQgdGhlIGBwYXJlbnRfc3RhdGlvbmAuDQoNCkZvciBtb3JlIGd1aWRhbmNlIG9uIGBkcGx5cmAgZnVuY3Rpb25zIChsaWtlIGBsZWZ0X2pvaW4oKWApIGNoZWNrIG91dCB0aGlzIFtjaGVhdCBzaGVldF0oaHR0cHM6Ly9ueXUtY2RzYy5naXRodWIuaW8vbGVhcm5pbmdyL2Fzc2V0cy9kYXRhLXRyYW5zZm9ybWF0aW9uLnBkZikNCg0KYGBge3J9DQojIGpvaW4gdGhlIGdlb21ldHJ5IG9mIHRoZSBkZXN0aW5hdGlvbiBzdGF0aW9uDQpnZW9tZXRyeV90b19qb2luIDwtIHNlbGVjdChzdG9wcyxpZCkgIyBkcm9wIHVubmVjZXNzYXJ5IGNvbHMNCmdlb21ldHJ5X3RvX2pvaW4gPC0gcmVuYW1lKGdlb21ldHJ5X3RvX2pvaW4sdG9faWQgPSAiaWQiKSAjIHJlbmFtZSAiaWQiIHRvICJ0b19pZCIgc28gdGhhdCB0aGUgZGF0YSBpcyBqb2luZWQgYXMgZXhwZWN0ZWQNCg0KVFRNIDwtDQogIGxlZnRfam9pbihUVE0sIA0KICAgICAgICAgICAgZ2VvbWV0cnlfdG9fam9pbiwNCiAgICAgICAgICAgIGJ5ID0gInRvX2lkIikNCg0KDQojIGpvaW4gdGhlIHN0YXRpb24gbmFtZSBvZiB0aGUgb3JpZ2luIHN0YXRpb24NCg0KbmFtZXNfdG9fam9pbiA8LSBzZWxlY3Qoc3RvcHMscGFyZW50X3N0YXRpb25fbmFtZSxpZCkgIyBkcm9wIHVubmVjZXNzYXJ5IGNvbHMNCm5hbWVzX3RvX2pvaW4gPC0gc3RfZHJvcF9nZW9tZXRyeShuYW1lc190b19qb2luKSAjIGRyb3AgZ2VvbWV0cnkNCm5hbWVzX3RvX2pvaW4gPC0gcmVuYW1lKG5hbWVzX3RvX2pvaW4sZnJvbV9pZCA9ICJpZCIpICMgcmVuYW1lICJpZCIgdG8gImZyb21faWQiIHNvIHRoYXQgdGhlIGRhdGEgaXMgam9pbmVkIGFzIGV4cGVjdGVkDQoNCg0KVFRNIDwtDQogIGxlZnRfam9pbihUVE0sIA0KICAgICAgICAgICAgbmFtZXNfdG9fam9pbiwNCiAgICAgICAgICAgIGJ5ID0gImZyb21faWQiKQ0KDQpUVE0gPC0gc3RfYXNfc2YoVFRNKQ0KDQpUVE0NCmBgYA0KDQpUaGlzIGRhdGEgZm9ybWF0IGlzIGVhc2llciB0byBpbnRlcnByZXQgaWYgd2UgbG9vayBhdCBhIHNpbmdsZSBzdG9wLiBGb3IgZXhhbXBsZSwgbGV0cyBmaW5kIGFsbCBzdG9wcyB0aGF0IGNhbiBiZSByZWFjaGVkIGZyb20gVGhlcmVzaWVuc3RyYcOfZSB3aXRoaW4gYSBtZWRpYW4gdHJhdmVsIHRpbWUgb2YgXDw9IDIwIG1pbnV0ZXMuDQoNCmBgYHtyfQ0KdHNfc3RvcHNfMjAgPC0gZmlsdGVyKFRUTSxwYXJlbnRfc3RhdGlvbl9uYW1lID09ICJUaGVyZXNpZW5zdHJhw59lIiwgdHJhdmVsX3RpbWVfcDUwIDw9IDIwKQ0KDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKSsNCiAgZ2VvbV9zZihkYXRhID0gdHNfc3RvcHNfMjAsIGNvbG9yID0gImxpZ2h0Ymx1ZSIpKw0KICBnZW9tX3NmKGRhdGEgPSBmaWx0ZXIoc3RvcHMscGFyZW50X3N0YXRpb25fbmFtZSA9PSAiVGhlcmVzaWVuc3RyYcOfZSIpLCBjb2xvciA9ICJibHVlIikNCmBgYA0KDQpMZXQncyBleHBvcnQgdGhpcyAoJiB0aGUgc3RvcHMpIHNvIHRoYXQgd2UgY2FuIHdvcmsgd2l0aCBpdCBpbiBRR0lTLg0KDQpgYGB7cn0NCnN0X3dyaXRlKFRUTSwiLi9vdXRwdXQvVFRNLmdwa2ciLGFwcGVuZCA9IEYpDQoNCnN0X3dyaXRlKHN0b3BzLCAiLi9vdXRwdXQvc3RvcHMuZ3BrZyIsIGFwcGVuZCA9IEYpDQpgYGANCg0KV2UgY2FuIGFjaGlldmUgdGhlIHNhbWUgcmVzdWx0IGFzIGFib3ZlIGJ5IGRvaW5nIHRoZSBmb2xsb3dpbmc6DQoNCiFbXSguL2ltZy9xZ2lzX3R0bS5wbmcpDQoNClRoZW4sIGJ5IHVzaW5nIHdoYXQgeW91J3ZlIGxlYXJuZWQgaW4gcHJldmlvdXMgdHV0b3JpYWxzLCB5b3UgY2FuIGNyZWF0ZSBzZXJ2aWNlIGFyZWFzOg0KDQohW10oLi9pbWcvc2VydmljZV9hcmVhX2V4YW1wbGUucG5nKQ0KDQpXZSd2ZSBqdXN0IHNjcmF0Y2hlZCB0aGUgc3VyZmFjZSBvZiB3aGF0IHlvdSBjYW4gZG8gd2l0aCBgcjVyYCBhbmQgYHRpZHl0cmFuc2l0YC4gQ29uc2lkZXIgdGFraW5nIGEgbG9vayB0aHJvdWdoIHRoZSBkb2N1bWVudGF0aW9uIGZvciBzb21lIGluc3BpcmF0aW9uIGZvciB5b3VyIHByb2plY3QhDQo=